<?php
/**
 * Ganglib
 *
 * LICENSE
 *
 * This source file is subject to the GNU Lesser Generak Public license.
 * 
 * It is available through the world-wide-web at this URL:
 * http://www.gnu.org/licenses/lgpl.html
 *
 * @category    Ganglib
 * @package    Div_Db_Distributor_Strategy_PDO
 * @copyright  Copyright (c) 2008 Ganglib project members (http://www.code.google.com/p/ganglib)
 * @license     http://www.gnu.org/licenses/lgpl.html
 */
 
/**
 * Horizontal Strategy
 * 
 * Horizontal Strategy allows You to partition database table over multiple
 * servers. It establishes a connection pool to available servers and uses one
 * of partition method (hash, list, range) to select server.
 *
 * @package    Div_Db_Distributor_Strategy_PDO
 * @copyright  Copyright (c) 2008 Ganglib project members (http://www.code.google.com/p/ganglib)
 * @license     http://www.gnu.org/licenses/lgpl.html
 */
require_once('Div/Db/Distributor/Strategy/PDO/Abstract.php'); 
class Div_Db_Distributor_Strategy_PDO_Horizontal extends Div_Db_Distributor_Strategy_PDO_Abstract 
{
	/**
	 * Query database
	 *
	 * @param Div_Db_Query $query
	 * @return PDO_Statement
	 */
	public function distributeQuery(Div_Db_Query $query)
	{
		$modelArray = $query->toModelArray();
		$rule = $this->_config->horizontal->{$modelArray['from'][0]}->rule;
		
		return call_user_func(array($this, '_'.$rule), $query);
	}
	

	private function _getQueryField($type)
	{
		switch($type)
		{
			case Div_Db_Query::QUERY_SELECT: $queryField = 'where'; break;
			case Div_Db_Query::QUERY_DELETE: $queryField = 'where'; break;
			case Div_Db_Query::QUERY_UPDATE: $queryField = 'where'; break;
			case Div_Db_Query::QUERY_INSERT: $queryField = 'set'; break;
		}
		
		return $queryField;
	}
	
	/**
	 * Combine single-element array from Div_Db_Query into "multi" elements array
	 *
	 * @param array $where
	 * @return array
	 * @access private
	 */
	private function _combineValuesArray($where = array())
	{
		if(!isset($where[0]))
			$where = array(0 => $where);
			
		return $where;
	}
	
	/**
	 * Returns number of elements in array
	 *
	 * @param string $where
	 * @access private
	 * @return int
	 */
	private function _numFields($where = array())
	{
		return isset($where[0]) ? count($where) : 1;
	}

	/**
	 * Range algorithm (highly experimental)
	 *
	 * @param Div_Db_Query $query
	 * @return PDO_Statement
	 */
	private function _range(Div_Db_Query $query)
	{	
		$modelArray = $query->toModelArray();
		$config = $this->_config->horizontal->{$modelArray['from'][0]}->toArray();
		$rules = $config[$config['rule']];
		
		$valueMin = null;
		$valueMax = null;
		$parts = $this->_combineValuesArray($modelArray[$this->_getQueryField($modelArray['type'])][$config['field']]);
		foreach($parts as $part)
		{
			$value = ltrim($part['value']);
			$operator = substr($value, 0, 2);
			
			if($operator == '<>' || $operator == '!=')
				self::throwException('NOT operators not allowed for horizontal strategy');
			
			if($operator == '>=')
				$valueMin = (int)substr($value, 2);
			
			if($operator == '<=')
				$valueMax = (int)substr($value, 2);
				
			if($operator[0] == '>' && !$valueMin)
				$valueMin = (int)substr($value, 1) + 1;
			
			if($operator[0] == '<' && !$valueMax)
				$valueMax = (int)substr($value, 1) - 1;
		}
		
		if(!$valueMax && !$valueMin)
			$valueMin = $valueMax = $parts[0]['value'];
			
		foreach($rules as $schema => $rule)
			if($valueMin >= $rule['min'] && $valueMax <= $rule['max'])
			{
				$connectionSchema = $schema;
				break;
			}

		if(!isset($connectionSchema))
			self::throwException('Could not determine connection schema for field');
		
		$connection = $this->connect($connectionSchema);
		
		return $this->runQuery($connection, $query);
	}
	
	/**
	 * Hash algorithm
	 *
	 * @param Div_Db_Query $query
	 * @return PDO_Statement
	 */
	private function _hash(Div_Db_Query $query)
	{
		$modelArray = $query->toModelArray();
		$config = $this->_config->horizontal->{$modelArray['from'][0]};
		$schemas = explode(',', $config->hash->schemas);
		
		$parts = $modelArray[$this->_getQueryField($modelArray['type'])][$config->field];
		if($this->_numFields($parts) > 1)
			self::throwException('Only one WHERE clause in hash is allowed');
		
		$queryField = $parts['value'];
		if(!is_numeric($queryField))
			self::throwException('Numeric field needed for horizontal strategy');
		
		$connectionSchema = (int)$queryField % count($schemas);
		return $this->connect($schemas[$connectionSchema])->query($query);
	}
	
	/**
	 * List
	 *
	 * @param Div_Db_Query $query
	 * @return PDO_Statement
	 */
	private function _list(Div_Db_Query $query)
	{
		$modelArray = $query->toModelArray();
		$config = $this->_config->horizontal->{$modelArray['from'][0]};
		
		$parts = $modelArray[$this->_getQueryField($modelArray['type'])][$config->field];
		if($this->_numFields($parts) > 1)
			self::throwException('Only one WHERE clause in hash is allowed');
		
		$queryField = $parts['value'];
		foreach($config->list->toArray() as $schema => $elements)
		{
			$elementsArray = explode(',', $elements['elements']);
			if(in_array($queryField, $elementsArray))
				$connectionSchema = $schema;
		}

		if(!isset($connectionSchema))
			self::throwException('Element not in list');
		
		return $this->connect($connectionSchema)->query($query);
	}
}